{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 2: Syft Kerasを用いた安全なモデル利用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "これで、通常のKerasで訓練されたモデルができたので、いくつかのプライバシーに配慮した予測の準備ができました。 Syft Kerasを使用してそれを行うことができます。\n",
    "\n",
    "このモデルを保護して提供するには、3つのTFEWorker（サーバー）が必要です。これは、内部でTF暗号化において [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation)と呼ばれる暗号化技術を使用しているためです。これは、モデルの重みと入力データをシェアに分割し、各値のシェアを異なるサーバーに送信するという考え方です。重要な箇所は、1つのサーバー上の共有を見ると、元の値（入力データまたはモデルの重み）について情報を与えていないということです。\n",
    "\n",
    "前のノートブックで行ったように、Syft Kerasモデルを定義します。ただし、トリックがあります。このモデルをインスタンス化する前に、`hook = sy.KerasHook(tf.keras)`を実行します。これにより、3つの重要な新しいメソッドがKeras Sequentialクラスに追加されます。\n",
    "\n",
    "`share`：秘密共有を利用してモデルを保護します。通常では、TF EncryptedのSecureNNプロトコルを使用して、3つのTFEWorkerのそれぞれの間でモデルを秘密に共有します。最も重要な点は、暗号化されたデータの予測を提供する機能が追加されることです。\n",
    "`serve`：この関数は、TFEWorkersが外部クライアントからの保護されたモデルの予測リクエストを受け入れることができるように、サービングキューを起動します。\n",
    "`shutdown_workers`：プライバシーに配慮した予測の提供が完了したら、この関数を実行してモデルをシャットダウンできます。各ワーカーを手動で管理することを選択した場合は、サーバープロセスを手動でシャットダウンするよう指示されます。\n",
    "\n",
    "MPCの詳細については、こちらの[blog](https://mortendahl.github.io/2017/04/17/private-deep-learning-with-mpc/)をご覧ください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras import Sequential\n",
    "from tensorflow.keras.layers import AveragePooling2D, Conv2D, Dense, Activation, Flatten, ReLU, Activation\n",
    "\n",
    "import syft as sy\n",
    "hook = sy.KerasHook(tf.keras)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 学習モデル"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ご覧のとおり、 `batch_input_shape`の提供を除いて、前回とほぼ同様の学習モデルを定義しています。これにより、TF Encryptedは事前定義されたテンソル形状を介して安全な計算を最適化できます。このMNISTデモでは、（1、28、28、1）の形状の入力データを送信します。\n",
    "また、この操作はMPCを使用して実行するのは複雑であり、予測要求を処理する必要がないため、softmaxではなくロジットを返します。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_classes = 10\n",
    "input_shape = (1, 28, 28, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Sequential()\n",
    "\n",
    "model.add(Conv2D(10, (3, 3), batch_input_shape=input_shape))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Conv2D(32, (3, 3)))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Conv2D(64, (3, 3)))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Flatten())\n",
    "model.add(Dense(num_classes, name=\"logit\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 学習済みの重みの読み込み"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "事前に学習済みのモデルの重みを`load_weights`によって、ローカル環境で読み出しすることが可能です。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pre_trained_weights = 'short-conv-mnist.h5'\n",
    "model.load_weights(pre_trained_weights)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ワーカーの呼び出し"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "TF Encryptedがプライバシーに配慮した予測を実行するために必要なTFEWorkers(`alice`, `bob`, `carol`) を作成しましょう。 TFEWorkerごとに、ホストを指定するだけです。次に、これらのワーカーをクラスターに結合します。\n",
    "\n",
    "これらのワーカーは[TensorFlow server](https://www.tensorflow.org/api_docs/python/tf/distribute/Server)を実行します。サーバーは手動で管理するか（AUTO = False）、ワーカーに管理を依頼する（AUTO = True）ことができます。それらを手動で管理することを選択した場合、以下のcluster.start（）を呼び出した後、各ワーカーのホストデバイスで端末コマンドを実行するように指示されます。すべてのワーカーが単一のデバイス（たとえば、localhost）でホストされている場合、SyftがワーカーのTensorFlowサーバーを自動的に管理するように選択できます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AUTO = False\n",
    "\n",
    "alice = sy.TFEWorker(host='localhost:4000', auto_managed=AUTO)\n",
    "bob = sy.TFEWorker(host='localhost:4001', auto_managed=AUTO)\n",
    "carol = sy.TFEWorker(host='localhost:4002', auto_managed=AUTO)\n",
    "\n",
    "cluster = sy.TFECluster(alice, bob, carol)\n",
    "cluster.start()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 重み共有によるモデル共有"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`sy.KerasHook（tf.keras）`により、 `share`メソッドを呼び出して、モデルをTF暗号化Kerasモデルに変換することが可能です。\n",
    "\n",
    "上記のサーバーを手動で管理するように要求した場合、サーバーがすべて起動されるまで、この手順は完了しません。ファイアウォールが着信接続を受け入れるためにPythonを要求する場合があることに注意してください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.share(cluster)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### モデルの運用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "パーフェクト！これで、`model.serve`,を呼び出すことで、モデルでプライベートな予測を提供する準備が整いました。 num_requestsを設定して、モデルが提供する予測リクエストの数に制限を設定できます。指定しない場合、中断されるまでモデルが提供されます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.serve(num_requests=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Part 13c** のノートブックに移動して、プライバシーを配慮した予測を行う準備ができました。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### まとめ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上記のリクエスト制限を超えると、モデルはリクエストを処理できなくなりますが、上記の3人のワーカー間で共有される秘密のままです。以下のセルを実行することにより、ワーカーの動作を終了させることができます。\n",
    "\n",
    "**おめでとう**パート13bのまとめ：Syft KerasとTFEによる安全な分類！"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.stop()\n",
    "cluster.stop()\n",
    "\n",
    "if not AUTO:\n",
    "    process_ids = !ps aux | grep '[p]ython -m tf_encrypted.player --config' | awk '{print $2}'\n",
    "    for process_id in process_ids:\n",
    "        !kill {process_id}\n",
    "        print(\"Process ID {id} has been killed.\".format(id=process_id))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
